import http from "http";
import https from "https";
import net, { AddressInfo } from "net";
import { SNISpoofer } from "./sni-spoofer";
import { EventEmitter } from "events";
import { FlowRateMonitor, FlowRateMonitorOptions } from "./FlowRateMonitor";
import { enableProxy, stopProxy } from "./setProxy";
import Request from "./Request";
import Response from "./Response";
import RequestContext from "./RequestContext";

interface HadaProxyEvents {
	start: [number];
	error: [Error];
	interceptedRequestError: [Error, InterceptedOps];
	interceptedResponseError: [Error, InterceptedOps];
	flowRateMonitor: [FlowRateMonitorOptions];
}

interface InterceptedOps {
	name?: string;
	/**
	 * 拦截的时机，request表示请求拦截，response表示响应拦截
	 */
	lifeCycle: "request" | "response";
	/**
	 * 返回值为true表示要拦截，false表示不拦截
	 * @param requestContext 请求上下文
	 * @returns
	 */
	test: (requestContext: RequestContext) => boolean;
}

interface InterceptedLisener {
	/**
	 * 拦截器配置
	 */
	ops: InterceptedOps;
	/**
	 * 注意：如果是拦截请求 也就是 lifeCycle="request"时，去设置响应的body, 会不往远程服务器发送请求，直接返回响应内容
	 * @param requestContext
	 * @returns
	 */
	callback: (requestContext: RequestContext) => Promise<void> | void;
	/**
	 * 是否启用此拦截器
	 */
	enabled: boolean;
}

export default class Proxy extends EventEmitter<HadaProxyEvents> {
	httpServer: http.Server;
	httpsServer?: https.Server;
	/**
	 * 上游代理
	 */
	upstreamProxy: string | undefined;
	/**
	 * 代理端口
	 */
	port: number;
	/**
	 * https证书
	 */
	certAuthority?: {
		key: string;
		cert: string;
	};
	interceptedLiseners: InterceptedLisener[] = [];

	private _requestListeners?: InterceptedLisener[];
	private _responseListeners?: InterceptedLisener[];
	flowRateMonitor: FlowRateMonitor;

	constructor({
		port,
		upstreamProxy,
		certAuthority,
	}: {
		port: number;
		upstreamProxy?: string;
		certAuthority?: {
			key: string;
			cert: string;
		};
	}) {
		super();
		this.port = port;
		this.upstreamProxy = upstreamProxy;
		this.certAuthority = certAuthority;

		this.flowRateMonitor = new FlowRateMonitor((ops) => {
			this.emit("flowRateMonitor", ops);
		});

		const httpServer = (this.httpServer = http.createServer(
			async (fromClient, toClient) => {
				this.flowRateMonitor.totalUploadFlow +=
					fromClient.socket.bytesRead;

				const request = new Request(fromClient, this);
				const response = new Response(this);
				const requestContext = new RequestContext(request, response);

				//拦截请求
				await this.interceptedRequest(requestContext);

				//如果请求在拦截中被设置了响应内容，则说明不需要往第三方服务器发送请求，直接返回响应内容

				let res;
				if (requestContext.response.body) {
					res = requestContext.response;
				} else {
					try {
						//发送到远程服务器
						res = await request.sendToServer();
					} catch (err: any) {
						toClient.writeHead(500, err.message);
						toClient.end(err.stack);
						return;
					}
				}

				//设置远程服务器响应
				response.setSource(res);

				//拦截响应
				await this.interceptedResponse(requestContext);

				response.sendToClient(toClient);
			}
		));

		httpServer.listen(port, () => {
			enableProxy({ host: "127.0.0.1", port });
			process.on("exit", this.handleExit);
			process.on("SIGINT", this.handleExit); // 当用户按下Ctrl+C时
			process.on("SIGTERM", this.handleExit); // 当系统要求应用程序退出时
			this.emit("start", port);
		});
		httpServer.on("error", (err) => {
			this.emit("error", err);
		});

		if (this.certAuthority) {
			const { key, cert } = this.certAuthority;
			const spoofer = new SNISpoofer(key, cert),
				SNICallback = spoofer.callback();

			const httpsServer = (this.httpsServer = https.createServer(
				{
					key,
					cert,
					SNICallback,
				},
				(fromClient, toClient) => {
					let shp = "https://" + fromClient.headers.host,
						fullUrl = shp + fromClient.url,
						addr = this.httpServer.address() as AddressInfo;
					let toServer = http.request(
						{
							host: "localhost",
							port: addr.port,
							method: fromClient.method,
							path: fullUrl,
							headers: fromClient.headers,
						},
						(fromServer) => {
							toClient.writeHead(
								fromServer.statusCode ?? 500,
								fromServer.statusMessage,
								fromServer.headers
							);
							fromServer.pipe(toClient);
							toServer.end();
						}
					);
					toServer.on("error", (err) => {
						this.emit("error", err);
					});
					fromClient.pipe(toServer);
				}
			));

			httpsServer.listen(0);
			httpsServer.on("error", (err) => {
				this.emit("error", err);
			});
		}

		const cxnEstablished = Buffer.from(
			`HTTP/1.1 200 Connection Established\r\n\r\n`,
			"ascii"
		);

		httpServer.on("connect", (request, clientSocket, head) => {
			let { port, hostname } = new URL(`http://${request.url}`);
			let numPort = parseInt(port);
			if (this.certAuthority) {
				let addr = this.httpsServer?.address() as AddressInfo;
				numPort = addr.port;
				hostname = addr.address;
			}
			let serverSocket = net.connect(numPort, hostname, () => {
				clientSocket.write(cxnEstablished);
				serverSocket.write(head);

				const readable = clientSocket.pipe(serverSocket);
				readable.pipe(clientSocket);

				this.flowRateMonitor.totalDownloadFlow +=
					Buffer.byteLength(head);
				readable.on("data", (chunk: any) => {
					this.flowRateMonitor.totalDownloadFlow += chunk.length;
				});

				clientSocket.on("error", (err) => {
					this.emit("error", err);
					clientSocket.destroy();
				});
				serverSocket.on("error", (err) => {
					this.emit("error", err);
					serverSocket.destroy();
				});
			});
		});
	}

	async interceptedRequest(requestContext: RequestContext) {
		const request = requestContext.request;
		if (!this._requestListeners) {
			this._requestListeners = this.interceptedLiseners.filter(
				(item) => item.ops.lifeCycle === "request"
			);
		}
		const listeners = this._requestListeners
			.filter((item) => item.enabled)
			.filter((item) => item.ops.test(requestContext));

		if (listeners.length > 0) {
			await request.readBody();
		}
		for (let index = 0; index < listeners.length; index++) {
			const listener = listeners[index];
			try {
				await listener.callback(requestContext);
			} catch (err: any) {
				this.emit("interceptedRequestError", err, listener.ops);
			}
		}
	}
	async interceptedResponse(requestContext: RequestContext) {
		const response = requestContext.response!;
		if (!this._responseListeners) {
			this._responseListeners = this.interceptedLiseners.filter(
				(item) => item.ops.lifeCycle === "response"
			);
		}
		const listeners = this._responseListeners
			.filter((item) => item.enabled)
			.filter((item) => item.ops.test(requestContext));
		if (listeners.length > 0) {
			await response.readBody();
		}
		for (let index = 0; index < listeners.length; index++) {
			const listener = listeners[index];
			try {
				await listener.callback(requestContext);
			} catch (err: any) {
				this.emit("interceptedResponseError", err, listener.ops);
			}
		}
	}

	intercepted(
		ops: InterceptedOps,
		callback: (requestContext: RequestContext) => Promise<void> | void
	) {
		const interceptedLisener = { ops, callback, enabled: true };
		this.interceptedLiseners.push(interceptedLisener);
		let refreshCache: () => void;
		if (ops.lifeCycle === "request") {
			refreshCache = () => {
				this._requestListeners = void 0;
			};
		}
		if (ops.lifeCycle === "response") {
			refreshCache = () => {
				this._responseListeners = void 0;
			};
		}
		return {
			cancel: () => {
				const index = this.interceptedLiseners.findIndex(
					(item) => item === interceptedLisener
				);
				if (index !== -1) {
					this.interceptedLiseners.splice(index, 1);
				}
				refreshCache();
			},
			pause: () => {
				interceptedLisener.enabled = false;
			},
			resume: () => {
				interceptedLisener.enabled = true;
			},
		};
	}

	async handleExit() {
		await stopProxy();
		console.log("系统代理关闭");
		process.exit();
	}
}
